'''
Created on Mar 19, 2014

@author: Eric
'''

from . import utils
from . import skeleton
from .skeleton import BinaryOpEnum
from .skeleton import UnaryOpEnum
from . import cell
from . import domain

#######

class UnresolvedReferenceError(Exception):
    
    def __init__(self, name):
        super(UnresolvedReferenceError, self).__init__(name)
        self.name = name
    
    def __str__(self):
        return "Unable to resolve name '%s'" % self.name

#######

class LiveObject(object):
    
    def __init__(self, parent, node):
        object.__setattr__(self, '_X_parent', parent)
        object.__setattr__(self, '_X_node', node)

    def _X_get_variable(self, name):
        if self._X_parent:
            return self._X_parent._X_get_variable(name)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_set_variable(self, name, value):
        if self._X_parent:
            self._X_parent._X_set_variable(name, value)
        else:
            raise UnresolvedReferenceError(name)

    def _X_get_cell(self, name):
        if self._X_parent:
            return self._X_parent._X_get_cell(name)
        else:
            raise UnresolvedReferenceError(name)
        
    def _X_set_cell(self, name, value):
        if self._X_parent:
            self._X_parent._X_set_cell(name, value)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_get_page(self, name):
        if self._X_parent:
            return self._X_parent._X_get_page(name)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_set_page(self, name, page):
        if self._X_parent:
            self._X_parent._X_set_page(name, page)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_call_fun(self, name, args):
        if self._X_parent:
            return self._X_parent._X_call_fun(name, args)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_call_mem(self, name, args):
        if self._X_parent:
            return self._X_parent._X_call_mem(name, args)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_call_proc(self, name, args):
        if self._X_parent:
            return self._X_parent._X_call_proc(name, args)
        else:
            raise UnresolvedReferenceError(name)
    
    def _X_closure(self):
        if self._X_parent:
            return self._X_parent._X_closure()
        else:
            return EnvironmentLiveObject(None)

    @utils.memoize
    def _X_file(self):
        return self._X_parent._X_file()

    @utils.memoize
    def _X_domain(self):
        return self._X_parent._X_domain()

#######

class DomainLiveObject(LiveObject):
    
    def __init__(self):
        super(DomainLiveObject, self).__init__(None, None)
        self._X_fwd_mapping = skeleton.parsed_domain()
        self._X_rev_mapping = dict((y, x) for (x, y) in self._X_fwd_mapping.items())
    
    def __getitem__(self, key):
        return FileLiveObject(self, self._X_fwd_mapping[key])

    def _X_domain(self):
        return self

#######

class FileLiveObject(LiveObject):

    def _X_file(self):
        return self
    
    @utils.memoize
    def _X_mod_path(self):
        return self._X_parent._X_rev_mapping[self._X_node]
    
    def __getattr__(self, name):
        for decl in self._X_node.decls:
            if decl.name == name:
                return PageConstructorLiveObject(self, decl)
        return super(FileLiveObject, self).__getattr__(name)

    def _X_get_page(self, name):
        for decl in self._X_node.decls:
            if isinstance(decl, skeleton.PageDeclNode):
                return PageConstructorLiveObject(self, decl)
            elif isinstance(decl, skeleton.ImportDeclNode):
                mod_path = domain.join(self._X_mod_path, decl.path)
                file_node = self._X_parent._X_fwd_mapping[mod_path]
                for decl in file_node.decls:
                    if isinstance(decl, skeleton.PageDeclNode):
                        return PageConstructorLiveObject(self, decl)
        return super(FileLiveObject, self)._X_get_page(name)

#######

class Rebase(object):
    
    def __init__(self, instance, name):
        self.instance = instance
        self.name = name

#######

class PageConstructorLiveObject(LiveObject):
    
    def __call__(self, **kwargs):
        return PageInstanceLiveObject(self, [(x, skeleton.ValueExprNode(None, y, None))
                                             for (x, y) in kwargs.items()])

    def build(self, args):
        return PageInstanceLiveObject(self, args)

#######

class PageInstanceLiveObject(LiveObject):
    
    def __init__(self, parent, args):
        super(PageInstanceLiveObject, self).__init__(parent, parent._X_node)
        super(PageInstanceLiveObject, self).__setattr__(self, '_X_cells', {})
        super(PageInstanceLiveObject, self).__setattr__(self, '_X_mems', {})
        super(PageInstanceLiveObject, self).__setattr__(self, '_X_procs', {})
        
        kwargs = dict(args)
        make_static = []
        for member in self._X_node.members:
            if isinstance(member, skeleton.InlineMemberNode):
                if isinstance(member.header, skeleton.SimpleHeaderNode):
                    if member.header.name in kwargs:
                        if isinstance(kwargs[member.header.name], Rebase):
                            rebase = kwargs[member.header.name]
                            self._X_cells[member.header.name] = rebase.instance._X_cells[rebase.name]
                        else:
                            self._X_cells[member.header.name] = kwargs[member.header.name]
                    else:
                        expr_lo = ExpressionLiveObject(self, member.expr)
                        self._X_cells[member.header.name] = cell.DynamicCell(expr_lo.value)
                        make_static.append(member.header.name)
                elif isinstance(member.header, skeleton.MemHeaderNode):
                    self._X_mems[member.header.name] = {}
            elif isinstance(member, skeleton.CodeMemberNode):
                if isinstance(member.header, skeleton.SimpleHeaderNode):
                    code_lo = CodeLiveObject(self, member.code)
                    self._X_cells[member.header.name] = cell.DynamicCell(code_lo)
                elif isinstance(member.header, skeleton.MemHeaderNode):
                    self._X_mems[member.header.name] = {}
            else:
                msg = "'%s' is an unrecognized member"
                assert False, msg % member.__class__.__name__
        for name in make_static:
            value = self._X_cells[name].get()
            self._X_cells[name] = cell.StaticCell(value)

    def __getattr__(self, name):
        if name in self._X_cells:
            return self._X_cells[name].get()
        else:
            return super(PageInstanceLiveObject, self).__getattr__(name)

    def __setattr__(self, name, value):
        if name in self._X_cells:
            self._X_cells[name].set(value)
        else:
            super(PageInstanceLiveObject, self).__setattr__(name, value)

    def _X_get_cell(self, name):
        if name in self._X_cells:
            return self._X_cells[name].get()
        else:
            return super(PageInstanceLiveObject, self)._X_get_cell(name)
    
    def _X_set_cell(self, name, value):
        if name in self._X_cells:
            self._X_cells[name].set(value)
        else:
            super(PageInstanceLiveObject, self)._X_set_cell(name, value)
            
#######

class ExpressionLiveObject(LiveObject):
    
    def value(self):
        if isinstance(self._X_node, skeleton.ValueExprNode):
            return self._X_node.value
        
        elif isinstance(self._X_node, skeleton.BinaryOpExprNode):
            left = ExpressionLiveObject(self, self._X_node.left).value()
            right = ExpressionLiveObject(self, self._X_node.right).value()
            if self._X_node.op == BinaryOpEnum.PLUS:
                return left + right
            elif self._X_node.op == BinaryOpEnum.MINUS:
                return left - right
            elif self._X_node.op == BinaryOpEnum.MULT:
                return left * right
            elif self._X_node.op == BinaryOpEnum.DIV:
                return left / right
            elif self._X_node.op == BinaryOpEnum.MOD:
                return left % right
            elif self._X_node.op == BinaryOpEnum.EXP:
                return left ** right
            elif self._X_node.op == BinaryOpEnum.LT:
                return left < right
            elif self._X_node.op == BinaryOpEnum.LE:
                return left <= right
            elif self._X_node.op == BinaryOpEnum.GT:
                return left > right
            elif self._X_node.op == BinaryOpEnum.GE:
                return left >= right
            elif self._X_node.op == BinaryOpEnum.EQ:
                return left == right
            elif self._X_node.op == BinaryOpEnum.NE:
                return left != right
            elif self._X_node.op == BinaryOpEnum.AND:
                return left and right
            elif self._X_node.op == BinaryOpEnum.OR:
                return left or right
            elif self._X_node.op == BinaryOpEnum.XOR:
                return left ^ right
            else:
                assert False, "Unregistered operation '%s'" % self._expr.op
        
        elif isinstance(self._expr, skeleton.UnaryOpExprNode):
            value = ExpressionLiveObject(self, self._X_node.expr).value()            
            if self._X_node.op == UnaryOpEnum.MINUS:
                return -value
            elif self._X_node.op == UnaryOpEnum.NOT:
                return not value
            else:
                assert False, "Unregistered operation '%s'" % self._expr.op
        
        elif isinstance(self._X_node, skeleton.VariableExprNode):
            return self._X_get_variable(self._X_node.name)
        
        elif isinstance(self._X_node, skeleton.CellExprNode):
            if self._X_node.expr:
                instance = ExpressionLiveObject(self, self._X_node.expr).value()
            else:
                instance = self
            return instance._X_get_cell(self._X_node.name)
        
        elif isinstance(self._X_node, skeleton.FunOrConstrExprNode):
            if self._X_node.expr:
                instance = ExpressionLiveObject(self, self._X_node.expr).value()
            else:
                instance = self
            try:
                return 
        elif isinstance(self._X_node, skeleton.TupleExprNode):
            return tuple([ExpressionLiveObject(self, x).value()
                          for x in self._X_node.values])
        
        elif isinstance(self._X_node, skeleton.ListExprNode):
            return [ExpressionLiveObject(self, x).value()
                    for x in self._X_node.values]
        
        elif isinstance(self._X_node, skeleton.MapExprNode):
            return dict([(ExpressionLiveObject(self, x).value(),
                          ExpressionLiveObject(self, y).value())
                         for (x, y) in self._X_node.values])
        
        elif isinstance(self._X_node, skeleton.SetExprNode):
            return set([ExpressionLiveObject(self, x).value()
                        for x in self._X_node.values])
        
        else:
            msg = "'%s' is an unrecognized expression"
            assert False, msg % self._expr.__class__.__name__

#######

class CodeLiveObject(LiveObject):
    
    def __init__(self, parent, node):
        super(CodeLiveObject, self).__init__(parent, node)
        self._X_pages = {}
        
    def run(self):
        for stmt in self._X_node.stmts:
            StatementLiveObject(self, stmt).run()

    def __call__(self):
        try:
            self.run()
        except ReturnException as e:
            return e.value

    def _X_get_page(self, name):
        if name in self._X_pages:
            return self._X_pages[name]
        else:
            for stmt in self._X_node.stmts:
                if isinstance(stmt, skeleton.ImportDeclNode):
                    mod_path = domain.join(self._X_file()._X_mod_path, stmt.path)
                    file_node = self._X_domain()._X_fwd_mapping[mod_path]
                    for decl in file_node.decls:
                        if isinstance(decl, skeleton.PageDeclNode):
                            return PageConstructorLiveObject(self, decl)
            return super(CodeLiveObject, self)._X_get_page(name)

    def _X_set_page(self, name, page):
        assert name not in self._X_pages
        self._X_pages[name] = page

#######

class ReturnException(Exception):
    
    def __init__(self, value):
        self.value = value

#######

class EnvironmentLiveObject(LiveObject):
    
    def __init__(self, parent):
        super(EnvironmentLiveObject, self).__init__(parent)
        self._X_base = None
        self._X_vars = {}

    def __getattr__(self, name):
        return getattr(self._X_base, name)
    
    def __call__(self, *args, **kwargs):
        return self._X_base(*args, **kwargs)

    def _X_get_variable(self, name):
        if name in self._X_vars:
            return self._X_vars[name]
        else:
            return super(EnvironmentLiveObject, self)._X_get_variable(name)
    
    def _X_set_variable(self, name, value):
        self._X_vars[name] = value
    
    def _X_closure(self):
        result = super(EnvironmentLiveObject, self)._X_closure()
        result._X_vars.update(self._X_vars)
        return result

#######

class StatementLiveObject(LiveObject):
    
    def run(self):
        if isinstance(self._X_node, skeleton.ReturnStmtNode):
            value = ExpressionLiveObject(self, self._X_node.expr).value()
            raise ReturnException(value)
        
        elif isinstance(self._X_node, skeleton.AssignStmtNode):
            value = ExpressionLiveObject(self, self._X_node.expr).value()
            self._X_set_variable(self._X_node.name, value)
        
        elif isinstance(self._X_node, skeleton.IfStmtNode):
            for case in self._X_node.cases:
                if case.expr:
                    value = ExpressionLiveObject(self, case.expr).value()
                    if value:
                        CodeLiveObject(self, case.code).run()
                        break
                else:
                    CodeLiveObject(self, case.code).run()
        
        elif isinstance(self._X_node, skeleton.SwitchStmtNode):
            test = ExpressionLiveObject(self, self._X_node.expr).value()
            for case in self._X_node.cases:
                if case.expr:
                    value = ExpressionLiveObject(self, case.expr).value()
                    match = test == value
                else:
                    match = True
                if match:
                    CodeLiveObject(self, case.code).run()
                    break
        
        elif isinstance(self._X_node, skeleton.PageDeclNode):
            env_lo = self._X_closure()
            env_lo._X_parent = self
            env_lo._X_base = PageConstructorLiveObject(self, self._X_node.decl)
            self._X_set_page(self._X_node.name, env_lo)
        
        elif isinstance(self._X_node, skeleton.ImportDeclNode):
            pass
        
        elif isinstance(self._X_node, skeleton.WhileStmtNode):
            first = True
            expr_lo = ExpressionLiveObject(self, self._X_node.expr)
            code_lo = CodeLiveObject(self, self._X_node.main_code)
            while expr_lo.value():
                first = False
                code_lo.run()
            if first and self._X_node.else_code:
                CodeLiveObject(self, self._X_node.else_code).run()
        
        elif isinstance(self._X_node, skeleton.DoUntilStmtNode):
            expr_lo = ExpressionLiveObject(self, self._X_node.expr)
            code_lo = CodeLiveObject(self, self._X_node.code)
            while True:
                code_lo.run()
                if expr_lo.value():
                    break
        
        else:
            msg = "'%s' is an unrecognized statement"
            assert False, msg % self._stmt.__class__.__name__
        
#######

def run(path):
    domain_lo = DomainLiveObject()
    return domain_lo.read(path)

